﻿using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using ScintillaNET;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace MyMesComp
{
    public partial class MesUserControl : UserControl
    {
        #region 构造函数
        public MesUserControl()
        {
            InitializeComponent();
            BeautifyControls();
            LoadOfflineData();
            InitializeDataGridView();
        }

        #endregion 构造函数

        #region 属性

        /// <summary>
        /// 需要编辑的对象
        /// </summary>
        private JObject Job { get; set; }

        private const string OfflineDataFilePath = "offline_data.json";

        private List<OfflineData> _offlineDataList;
        public class OfflineData
        {
            public string ApiUrl { get; set; }
            public object Data { get; set; }
            public string Timestamp { get; set; }
        }
        #endregion 属性

        #region 控件事件

        /// <summary>
        /// Load事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void MesUserControl_Load(object sender, EventArgs e)
        {
            btn_load.Click += Btn_load_Click;
            Btn_load_Click(null, null);
            btn_saveConfigFile.Click += Btn_saveConfigFile_Click;
            bt_RetrySend.Click += bt_RetrySend_Click;
        }

        /// <summary>
        /// 保存配置文件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        /// <exception cref="NotImplementedException"></exception>
        private void Btn_saveConfigFile_Click(object sender, EventArgs e)
        {
            try
            {
                var file = FindConfigJsonFiles("MesApiConfigs").FirstOrDefault();
                if (file != null)
                {
                    var contents = editOfAllConfigs.Text;
                    var jObject = contents.Deserialize<JObject>();
                    File.WriteAllText(file, contents, Encoding.UTF8);
                    MessageBox.Show(@"保存成功");
                }
                else
                {
                    MessageBox.Show(@"文件MesApiConfigs.Json不存在", @"错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show($@"保存异常：{ex.Message}");
            }
        }

        private void CreateEditControls(JToken token, Control parent)
        {
            int x = 10, y = 10;
            int maxWidth = parent.Width - 20;

            if (token is JObject jObject)
            {
                foreach (var property in jObject.Properties())
                {
                    var label = new Label
                    {
                        Text = property.Name,
                        AutoSize = true,
                        Location = new Point(x, y)
                    };
                    parent.Controls.Add(label);
                    x += label.Width + 10;

                    if (property.Value is JObject || property.Value is JArray)
                    {
                        var button = new Button
                        {
                            Text = "Edit",
                            Tag = property.Value,
                            Location = new Point(x, y)
                        };
                        button.Click += (sender, e) =>
                        {
                            var btn = sender as Button;
                            var subToken = btn.Tag as JToken;
                            var subPanel = new Panel
                            {
                                Dock = DockStyle.Top,
                                AutoScroll = true
                            };
                            parent.Controls.Add(subPanel);
                            CreateEditControls(subToken, subPanel);
                        };
                        parent.Controls.Add(button);
                        x += button.Width + 10;
                    }
                    else
                    {
                        var textBox = new TextBox
                        {
                            Text = property.Value.ToString(),
                            Tag = property,
                            Location = new Point(x, y)
                        };
                        textBox.TextChanged += (sender, e) =>
                        {
                            var tb = sender as TextBox;
                            var prop = tb.Tag as JProperty;
                            prop.Value = tb.Text;
                        };
                        parent.Controls.Add(textBox);
                        x += textBox.Width + 10;
                    }

                    if (x > maxWidth)
                    {
                        x = 10;
                        y += 30;
                    }
                }
            }
            else if (token is JArray jArray)
            {
                for (int i = 0; i < jArray.Count; i++)
                {
                    var label = new Label
                    {
                        Text = $"[{i}]",
                        AutoSize = true,
                        Location = new Point(x, y)
                    };
                    parent.Controls.Add(label);
                    x += label.Width + 10;

                    var button = new Button
                    {
                        Text = "Edit",
                        Tag = jArray[i],
                        Location = new Point(x, y)
                    };
                    button.Click += (sender, e) =>
                    {
                        var btn = sender as Button;
                        var subToken = btn.Tag as JToken;
                        var subPanel = new Panel
                        {
                            Dock = DockStyle.Top,
                            AutoScroll = true
                        };
                        parent.Controls.Add(subPanel);
                        CreateEditControls(subToken, subPanel);
                    };
                    parent.Controls.Add(button);
                    x += button.Width + 10;

                    if (x > maxWidth)
                    {
                        x = 10;
                        y += 30;
                    }
                }
            }
        }

        /// <summary>
        /// 美化控件
        /// </summary>
        private void BeautifyControls()
        {
            foreach (Control control in this.Controls)
            {
                control.Font = new Font("Arial", 10, FontStyle.Regular);
                control.BackColor = Color.LightBlue;
                control.ForeColor = Color.DarkBlue;
                if (control is Button button)
                {
                    button.FlatStyle = FlatStyle.Flat;
                    button.FlatAppearance.BorderSize = 1;
                    button.FlatAppearance.BorderColor = Color.DarkBlue;
                }
                else if (control is TextBox textBox)
                {
                    textBox.BorderStyle = BorderStyle.FixedSingle;
                }
                else if (control is ComboBox comboBox)
                {
                    comboBox.FlatStyle = FlatStyle.Flat;
                }
                else if (control is DataGridView dataGridView)
                {
                    dataGridView.BackgroundColor = Color.LightBlue;
                    dataGridView.DefaultCellStyle.BackColor = Color.White;
                    dataGridView.DefaultCellStyle.ForeColor = Color.DarkBlue;
                    dataGridView.ColumnHeadersDefaultCellStyle.BackColor = Color.DarkBlue;
                    dataGridView.ColumnHeadersDefaultCellStyle.ForeColor = Color.White;
                }
            }
        }

        /// <summary>
        /// 加载事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        /// <exception cref="NotImplementedException"></exception>
        private void Btn_load_Click(object sender, EventArgs e)
        {
            try
            {
                var file = FindConfigJsonFiles("MesApiConfigs").FirstOrDefault();
                if (file != null)
                {
                    string jsonString = File.ReadAllText(file, Encoding.UTF8);
                    Job = JsonConvert.DeserializeObject(jsonString, typeof(JObject)) as JObject;
                    FillComboBoxData();
                    cb_url.SelectedIndexChanged += Cb_url_SelectedIndexChanged;
                    cb_url.SelectedIndex = 0;
                    btn_Send.Click += Btn_Send_Click;
                    rtb_log.BackColor = Color.FromArgb(199, 237, 204);
                    editOfAllConfigs.BackColor = editor.BackColor = Color.FromArgb(199, 237, 204);
                    editOfAllConfigs.Text = BeautifulJsonText(jsonString);
                    {
                        var nums = editOfAllConfigs.Margins[1];
                        nums.Width = 30;
                        nums.Type = MarginType.Number;
                        nums.Sensitive = true;
                        nums.Mask = 0;
                    }
                    {
                        var nums = editor.Margins[1];
                        nums.Width = 30;
                        nums.Type = MarginType.Number;
                        nums.Sensitive = true;
                        nums.Mask = 0;
                    }
                }
                else
                {
                    AddLog($"未找到测试MES的配置文件:MesApiConfigs.json", true);
                }
            }
            catch (Exception ex)
            {
                AddLog($@"加载异常：{ex.Message}", true);
            }
        }

        /// <summary>
        /// 发送事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        /// <exception cref="NotImplementedException"></exception>
        private void Btn_Send_Click(object sender, EventArgs e)
        {
            try
            {
                btn_Send.Enabled = false;
                var job = CollectValuesAndReplaceJson();
                var url = cb_url.SelectedItem.ToString();
                var uri = new Uri(url);
                var domain = uri.Host;
                var port = uri.Port;
                // 测试域名和端口是否可连接

                Task.Run(() =>
                {
                    using (var tcpClient = new TcpClient())
                    {
                        try
                        {
                            tcpClient.Connect(domain, port);
                        }
                        catch (SocketException ex)
                        {
                            rtb_log.InvokeAction(() =>
                            {
                                AddLog($"url:{url}\r\n无法连接到 {domain}:{port}\r\n{ex.Message}", true);
                                AddOfflineData(url, job);
                            });

                            btn_Send.InvokeAction(() =>
                            {
                                btn_Send.Enabled = true;
                            });
                            return;
                        }
                    }
                    try
                    {
                        rtb_log.InvokeAction(() => { AddLog($"发送数据：{job.ToJsonFormatted()}"); });
                        //ar postResult = PostHelper.Post(url, job.ToJsonFormatted(), SendPostMode.Json);
                        var postResult = PostHelper.SendHttpPost(url, job.ToJsonFormatted());
                        rtb_log.InvokeAction(() => { AddLog(postResult); });
                    }
                    catch (Exception ex)
                    {
                        rtb_log.InvokeAction(() => { AddLog($"发送异常：{ex.Message}", true); });
                        AddOfflineData(url, job);
                    }
                    finally
                    {
                        btn_Send.InvokeAction(() =>
                        {
                            btn_Send.Enabled = true;
                        });
                    }
                });
            }
            catch (Exception ex)
            {               
                AddLog($"异常：{ex.Message}", true);
            }
        }      

    /// <summary>
    /// URL选择事件
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    /// <exception cref="NotImplementedException"></exception>
    private void Cb_url_SelectedIndexChanged(object sender, EventArgs e)
        {
            var url = cb_url.SelectedItem + "";
            var job = Job.SelectTokens($"$.Apis[?(@.ApiUrl=='{url}')]").FirstOrDefault();
            if (job is JObject job1)
            {
                var userSendJObject = job1["SendModel"].As<JObject>();
                editor.Text = userSendJObject.ToJsonFormatted();
                var toReplaceJArray = job1["ToReplaceProperties"].As<JArray>().Select(p => p.Value<string>()).ToList();
                List<Tuple<string, JTokenType, JToken>> toReplaceProperties = new List<Tuple<string, JTokenType, JToken>>();
                ForeachJObjectProperty(userSendJObject, (p, parent) =>
                {
                    if (toReplaceJArray.Contains(p.Name))
                    {
                        toReplaceProperties.Add(Tuple.Create(p.Name, p.Value.Type, p.Value));
                    }
                    return true;
                }, true);
                FillDataGridView(toReplaceProperties);
            }
            #region 离线上传
            var selectedUrl = cb_url.SelectedItem?.ToString();
            if (string.IsNullOrEmpty(selectedUrl)) return;
            if (_offlineDataList!=null)
            {
                var filteredData = _offlineDataList.Where(data => data.ApiUrl == selectedUrl).ToList();
                dgv_retryData.Rows.Clear();
                foreach (var data in filteredData)
                {
                    dgv_retryData.Rows.Add(data.ApiUrl, data.Data.ToString(), data.Timestamp);
                }
            }
            #endregion
        }
        /// <summary>
        /// 填充ComboBox数据
        /// </summary>
        private void FillComboBoxData()
        {
            cb_url.Items.Clear();
            foreach (JObject job1 in Job["Apis"])
            {
                cb_url.Items.Add(job1["ApiUrl"].Value<string>());
            }
        }

        #endregion 控件事件

        #region 内部函数

        /// <summary>
        /// 填充DataGirdView
        /// </summary>
        /// <param name="arr"></param>
        private void FillDataGridView(List<Tuple<string, JTokenType, JToken>> arr)
        {
            dgv_parameters.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.AllCells;
            dgv_parameters.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
            if (dgv_parameters.Columns.Count == 0)
            {
                dgv_parameters.Columns.Add(new DataGridViewTextBoxColumn()
                {
                    HeaderText = @"ToReplaceProperty",
                    MinimumWidth = 6,
                    ReadOnly = true,
                    Name = "ToReplaceProperty",
                    SortMode = DataGridViewColumnSortMode.NotSortable,
                    Width = 110,
                });
                dgv_parameters.Columns.Add(new DataGridViewTextBoxColumn()
                {
                    HeaderText = @"Value(Editable)",
                    MinimumWidth = 6,
                    ReadOnly = false,
                    Name = "Value",
                    SortMode = DataGridViewColumnSortMode.NotSortable,
                    DefaultCellStyle = new DataGridViewCellStyle { WrapMode = DataGridViewTriState.True },
                    Width = 110,
                    AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells,
                });
                dgv_parameters.Columns.Add(new DataGridViewTextBoxColumn()
                {
                    HeaderText = @"Type",
                    MinimumWidth = 6,
                    ReadOnly = true,
                    Name = "Type",
                    SortMode = DataGridViewColumnSortMode.NotSortable,
                    Width = 110,
                });
                dgv_parameters.Columns.Add(new DataGridViewTextBoxColumn()
                {
                    HeaderText = @"Description",
                    MinimumWidth = 6,
                    ReadOnly = true,
                    Name = "Description",
                    SortMode = DataGridViewColumnSortMode.NotSortable,
                    Width = 110,
                });
            }

            dgv_parameters.Rows.Clear();
            var loadFromDisk = LoadFromDisk();

            foreach (var (toReplacePropertyName, tokenType, jToken) in arr)
            {
                var addedIndex = dgv_parameters.Rows.Add();
                var toEditRow = dgv_parameters.Rows[addedIndex];
                toEditRow.Cells["ToReplaceProperty"].Value = toReplacePropertyName;
                toEditRow.Cells["Value"].Value = SearchData(toReplacePropertyName, jToken.ToString());
                toEditRow.Cells["Type"].Value = tokenType.ToString();
                toEditRow.Cells["Description"].Value = string.Empty;
            }

            dgv_parameters.Tag = arr;
            return;

            string SearchData(string key, string defaultVal = "")
            {
                var url = cb_url.SelectedItem.ToString();
                var key1 = $"{url}.{key}";
                return loadFromDisk.TryGetValue(key1, out var data) ? data : defaultVal;
            }
        }

        /// <summary>
        /// 收集值并替换Json后返回JObject对象
        /// </summary>
        /// <returns></returns>
        private JObject CollectValuesAndReplaceJson()
        {
            var job = editor.Text.Deserialize<JObject>();
            List<Tuple<string, JTokenType, JToken>> tuples = dgv_parameters.Tag.As<List<Tuple<string, JTokenType, JToken>>>();
            if (tuples == null)
            {
                return null;
            }

            Dictionary<string, string> listDic = LoadFromDisk();
            var url = cb_url.SelectedItem.ToString();
            foreach (DataGridViewRow row in dgv_parameters.Rows)
            {
                string property = row.Cells["ToReplaceProperty"].Value.ToString();
                string value = row.Cells["Value"].Value.ToString();
                string key = $"{url}.{property}";
                listDic[key] = value;
                ForeachJObjectProperty(job, (p, jParent) =>
                {
                    var firstOrDefault = tuples.FirstOrDefault(p1 => p1.Item1 == p.Name && p1.Item1 == property);
                    if (firstOrDefault?.Item1 != null)
                    {
                        var val = (JToken)ChangeType(value, firstOrDefault.Item2);
                        jParent[property] = val;
                        return false;
                    }
                    return true;
                });
            }
            Save(listDic);
            return job;
        }

        private object ChangeType(string value, JTokenType type)
        {
            switch (type)
            {
                case JTokenType.None:
                    break;

                case JTokenType.Object:
                    return JObject.Parse(value);

                case JTokenType.Array:
                    return JArray.Parse(value);
                    break;

                case JTokenType.Constructor:
                    break;

                case JTokenType.Property:
                    break;

                case JTokenType.Comment:
                    break;

                case JTokenType.Integer:
                    return new JValue(Convert.ChangeType(value, typeof(int)));
                    break;

                case JTokenType.Float:
                    return new JValue(Convert.ChangeType(value, typeof(float)));
                    break;

                case JTokenType.String:
                    return new JValue(value);

                case JTokenType.Boolean:
                    return new JValue(Convert.ChangeType(value, typeof(bool)));
                    break;

                case JTokenType.Null:
                    break;

                case JTokenType.Undefined:
                    break;

                case JTokenType.Date:
                    break;

                case JTokenType.Raw:
                    break;

                case JTokenType.Bytes:
                    break;

                case JTokenType.Guid:
                    break;

                case JTokenType.Uri:
                    break;

                case JTokenType.TimeSpan:
                    break;

                default:
                    throw new ArgumentOutOfRangeException(nameof(type), type, null);
            }

            return null;
        }

        private bool ForeachJObjectProperty(JObject job, Func<JProperty, JObject, bool> action, bool loopJObject = false, bool loopArray = false)
        {
            if (job == null) return true;
            var properties = job.Properties();
            foreach (var property in properties)
            {
                var result = action(property, job);
                if (result == false)
                {
                    return false;
                }
                if (property.Value.IsJObject())
                {
                    if (loopJObject)
                    {
                        var foreachJObjectProperty = ForeachJObjectProperty(property.Value.As<JObject>(), action, true);
                        if (foreachJObjectProperty == false)
                        {
                            return false;
                        }
                    }
                }
                else if (property.Value.IsJArray())
                {
                    var list = property.Value.As<JArray>().ToList();
                    foreach (JToken jToken in list)
                    {
                        var indexOf = list.IndexOf(jToken);
                        if (jToken is JObject job1)
                        {
                            if (loopArray)
                            {
                                var foreachJObjectProperty = ForeachJObjectProperty(job1, action, true);
                                if (foreachJObjectProperty == false)
                                {
                                    return false;
                                }
                            }
                        }
                        else
                        {
                            //JValue
                        }
                    }
                }
            }

            return true;
        }

        private void AddLog(string msg, bool isError = false)
        {
            rtb_log.Text += $"{msg}\r\n";
            rtb_log.BackColor = isError ? Color.FromArgb(255, 199, 199) : Color.FromArgb(199, 237, 204);
        }

        private static string BeautifulJsonText(string allText)
        {
            allText = allText.Deserialize<object>().ToJsonFormatted();
            var count1 = new Regex(@"\s").Replace(allText, "").Length;
            var re = new Regex(@"\{[^\{\}]*\}");
            var matches = re.Matches(allText);
            foreach (Match match in matches)
            {
                try
                {
                    var json1 = match.Value.Deserialize<object>().ToJson();
                    allText = allText.Replace(match.Value, json1);
                }
                catch (Exception ex)
                {
                    throw ex;
                }
            }

            var count2 = new Regex(@"\s").Replace(allText, "").Length;

            Regex re1 = new Regex(@"(?<=(""""|""[^""]+""|true|false|\d+|null))\s*,\s*\r?\n\s*(?=""\w+""\s*:\s*(""""|""[^""]+""|true|false|\d+|null))", RegexOptions.Multiline);
            allText = re1.Replace(allText, ",");
            var count3 = new Regex(@"\s").Replace(allText, "").Length;
            if (count3 != count1)
            {
                throw new Exception($"格式化时，字符不匹配异常 {count3} != {count1}");
            }
            return allText;
        }

        private List<string> FindConfigJsonFiles(string fileName)
        {
            List<string> configFiles = new List<string>();
            try
            {
                foreach (var file in Directory.GetFiles(".", $"*{fileName}*.json", SearchOption.AllDirectories))
                {
                    configFiles.Add(file);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show($@"查找文件异常：{ex.Message}");
            }
            return configFiles;
        }

        private void Save(Dictionary<string, string> toSaveList)
        {
            try
            {
                string serializedData = JsonConvert.SerializeObject(toSaveList);
                File.WriteAllText("TempSave.bin", serializedData, Encoding.UTF8);
            }
            catch (Exception ex)
            {
                MessageBox.Show($@"保存数据时发生异常：{ex.Message}");
            }
        }

        private Dictionary<string, string> LoadFromDisk()
        {
            try
            {
                if (File.Exists("TempSave.bin"))
                {
                    string serializedData = File.ReadAllText("TempSave.bin", Encoding.UTF8);
                    return JsonConvert.DeserializeObject<Dictionary<string, string>>(serializedData);
                }
                else
                {
                    return new Dictionary<string, string>();
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show($@"加载数据时发生异常：{ex.Message}");
                return new Dictionary<string, string>();
            }
        }
        #region 离线上传
        private void LoadOfflineData()
        {
            if (File.Exists(OfflineDataFilePath))
            {
                string json = File.ReadAllText(OfflineDataFilePath, Encoding.UTF8);
                _offlineDataList = JsonConvert.DeserializeObject<List<OfflineData>>(json) ?? new List<OfflineData>();
                //dgv_retryData.DataSource = _offlineDataList;
            }
            else
            {
                _offlineDataList = new List<OfflineData>();
            }
        }

        private void InitializeDataGridView()
        {
            dgv_retryData.Columns.Clear();
            dgv_retryData.Columns.Add("ApiUrl", "API URL");
            dgv_retryData.Columns.Add("Data", "Data");
            dgv_retryData.Columns.Add("Timestamp", "Timestamp");
        }
        private  void bt_RetrySend_Click(object sender, EventArgs e)
        {
            var selectedUrl = cb_url.SelectedItem?.ToString();
            if (string.IsNullOrEmpty(selectedUrl)) return;

            var dataToRetry = _offlineDataList.Where(data => data.ApiUrl == selectedUrl).ToList();
            foreach (var data in dataToRetry)
            {
                try
                {
                    var postResult = PostHelper.SendHttpPost(data.ApiUrl, data.Data.ToString());                   
                    _offlineDataList.Remove(data);
                    UpdateOfflineDataFile();
                    dgv_retryData.Rows.RemoveAt(dgv_retryData.CurrentRow.Index);
                    AddLog($"数据上传成功：{data.Data}");
                }
                catch (Exception ex)
                {
                    AddLog($"数据上传失败：{ex.Message}", true);
                }
            }
        }

        private void UpdateOfflineDataFile()
        {
            string json = JsonConvert.SerializeObject(_offlineDataList, Formatting.Indented);
            File.WriteAllText(OfflineDataFilePath, json, Encoding.UTF8);
        }

        private void AddOfflineData(string apiUrl, JObject data)
        {
            var offlineData = new OfflineData
            {
                ApiUrl = apiUrl,
                Data = data,
                Timestamp = DateTime.Now.ToString()
            };
            _offlineDataList.Add(offlineData);
            UpdateOfflineDataFile();
        }
        #endregion
        #endregion 内部函数

    }
}